﻿using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace Innovative.Atom.Timer.Event
{
	public abstract class AtomEvent : IAtomEvent
	{
		public event EventHandler<AtomExceptionEventArgs> TaskErrorOccurred = null;
		public event EventHandler<AtomGetTaskEventArgs> GetNextTask = null;

		private DateTime _createdDateTime = DateTime.Now;
		private string _eventName = string.Empty;
		private DateTime _eventDateTime = DateTime.MinValue;
		private Task<bool> _eventTask = NullTask.Empty;
		private object _state = null;
		private bool _taskIsExpired = false;
		private long _eventId = 0;

		public AtomEvent()
		{
		}

		public AtomEvent(string eventName)
		{
			this.EventName = eventName;
		}

		public AtomEvent(Task<bool> eventTask)
		{
			this.EventTask = eventTask;
		}

		public AtomEvent(string eventName, Task<bool> eventTask)
		{
			this.EventName = eventName;
			this.EventTask = eventTask;
		}

		public virtual DateTime CreatedDateTime
		{
			get
			{
				return _createdDateTime;
			}
			internal set
			{
				_createdDateTime = value;
			}
		}

		public virtual string EventName
		{
			get
			{
				return _eventName;
			}
			set
			{
				_eventName = value;
			}
		}

		public virtual DateTime EventDateTime
		{
			get
			{
				return _eventDateTime;
			}
			internal set
			{
				_eventDateTime = value;
			}
		}

		public long EventId
		{
			get
			{
				return _eventId;
			}
			private set
			{
				_eventId = value;
			}
		}

		public virtual void InitializeNextEventDateTime()
		{
			// ***
			// *** Increment the counter when the task is completed
			// ***
			this.IncrementEventId();

			// ***
			// *** Let the class perform the logic
			// ***
			this.OnInitializeNextEventDateTime();
		}

		public virtual void UpdateNextEventDateTime()
		{
			// ***
			// *** Increment the counter when the task is completed
			// ***
			this.IncrementEventId();

			// ***
			// *** Let the class perform the logic
			// ***
			this.OnUpdateNextEventDateTime();
		}

		public virtual void RequestNextTask()
		{
			// ***
			// *** Remove the current task and dispose
			// ***
			if (this.EventTask != null && this.EventTask.Status == TaskStatus.RanToCompletion)
			{
				this.EventTask.Dispose();
				this.EventTask = NullTask.Empty;
			}

			if (!this.TaskIsExpired)
			{
				// ***
				// *** Request the next task
				// ***
				Task<bool> nextTask = this.OnRequestNextTask();

				if (nextTask != null)
				{
					// ***
					// *** Save this new task
					// ***
					this.EventTask = nextTask;
				}
				else
				{
					// ***
					// *** The task cannot be null, throw an exception...
					// ***
					throw new AtomNullTaskException();
				}
			}
		}

		public virtual void TaskStarting(AtomEventData atomEventData)
		{
			Trace.TraceInformation("Event '{0}' was started at {1} {2}", this.EventName, DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString());
			Trace.TraceInformation("Offset from expected start time is {0}", atomEventData.Offset);
			this.OnTaskStarting(atomEventData);
		}

		public virtual void TaskCompleted()
		{
			Trace.TraceInformation("The event '{0}' completed on {1} at {2}", this.EventName, DateTime.Now.ToShortDateString(), DateTime.Now.ToLongTimeString());
			this.OnTaskCompleted();
		}

		public virtual void TaskError(AtomException taskException)
		{
			this.OnTaskError(taskException);
		}

		public virtual Task<bool> EventTask
		{
			get
			{
				if (_eventTask == null)
				{
					_eventTask = this.OnRequestNextTask();
				}

				return _eventTask;
			}
			set
			{
				_eventTask = value;

				if (_eventTask == null)
				{
					throw new AtomNullTaskException();
				}
			}
		}

		public virtual object State
		{
			get
			{
				return _state;
			}
			set
			{
				_state = value;
			}
		}

		public virtual TimeSpan TaskDueTime
		{
			get
			{
				// ***
				// *** Get the amount of time until the next event
				// ***
				TimeSpan returnValue = this.EventDateTime.Subtract(DateTime.Now);

				// ***
				// *** If the event is in the past fire it now
				// ***
				if (returnValue < TimeSpan.Zero)
				{
					returnValue = TimeSpan.Zero;
				}

				return returnValue;
			}
		}

		public virtual bool TaskIsExpired
		{
			get
			{
				return _taskIsExpired;
			}
			internal set
			{
				_taskIsExpired = value;
			}
		}

		internal void IncrementEventId()
		{
			Interlocked.Increment(ref _eventId);
		}

		protected virtual void OnInitializeNextEventDateTime()
		{
		}

		protected virtual void OnUpdateNextEventDateTime()
		{
		}

		protected virtual void OnTaskStarting(AtomEventData atomEventData)
		{
		}

		protected virtual Task<bool> OnRequestNextTask()
		{
			Task<bool> returnValue = null;

			AtomGetTaskEventArgs e = new AtomGetTaskEventArgs();
			this.OnGetNextTask(e);
			returnValue = e.NextTask;

			return returnValue;
		}

		protected virtual void OnTaskError(AtomException taskException)
		{
			this.OnTaskErrorOccurred(new AtomExceptionEventArgs(taskException));
		}

		protected virtual void OnTaskCompleted()
		{
		}

		protected virtual void OnTaskErrorOccurred(AtomExceptionEventArgs e)
		{
			if (this.TaskErrorOccurred != null)
			{
				this.TaskErrorOccurred(this, e);
			}
		}

		protected virtual void OnGetNextTask(AtomGetTaskEventArgs e)
		{
			if (this.GetNextTask != null)
			{
				this.GetNextTask(this, e);
			}
		}
	}
}
